package com.example.chinesenation.Utils;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.chinesenation.Entity.*;
import com.example.chinesenation.Exception.AuthenException;
import com.example.chinesenation.Redis.RedisService;
import com.example.chinesenation.Response.ResultType;
import com.example.chinesenation.Service.*;
import com.example.chinesenation.VO.MainPage.VOArticleList;
import com.example.chinesenation.VO.MainPage.VOPolicyList;
import com.example.chinesenation.VO.UserInterestJSONObject;
import com.example.chinesenation.VO.keyWordJSONObject;
import javafx.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Async;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.IntFunction;
import java.util.stream.Collectors;

import static com.example.chinesenation.Utils.CommonURL.CategoryList;

/**
 * @author sakuraFallingDown
 * @version 1.0
 * @date 2021/3/4 12:59
 */
@Configuration
public class RecommendedTools {//推荐部分代码（基于内容的推荐）

    private final static Logger logger= LoggerFactory.getLogger(RecommendedTools.class);

    public static final int InterestSize=11;
    @Autowired
    private IArticleService articleService;
    @Autowired
    private IPolicyListService policyListService;

    @Autowired
    private IKeyWordsService keyWordsService;

    @Autowired
    private IUserInterestService userInterestService;

    @Autowired
    private IKeyWordsPolicyService keyWordsPolicyService;

    @Autowired
    private RedisService redisService;

    private ConcurrentHashMap<String,List<Double>> userInterestMap=new ConcurrentHashMap<>();

    private final List<String> interestList = Arrays.asList(CategoryList);//用户在文章信息推荐系统所对应的各个模块的权重所对应的模块名



    //为用户推荐民族信息内容
    public List<VOArticleList> recommendedArticle(String uid){

        List<Double> userMainInterest = redisService.getUserMainInterest(uid).stream().map(o ->(Double)o).collect(Collectors.toList());
        if(userMainInterest.size() == 0){
            redisService.initUserInterest(uid);
            userMainInterest=redisService.getUserMainInterest(uid).stream().map(o ->(Double)o).collect(Collectors.toList());
        }
        List<List<Double>> userInterest=new ArrayList<>();
        for(int index=0;index<InterestSize-1;index++){//民族信息部分为前10个兴趣矩阵
            List<Double> doubles=redisService.getUserInterest(uid,index).stream().map(o ->(Double)o).collect(Collectors.toList());
            userInterest.add(doubles);
        }
        int n=new Random().nextInt(100);
        IPage<KeyWords> page=new Page<>(n,100);
        List<KeyWords> list = keyWordsService.page(page).getRecords();
        return getTop10CosArticleForUser(userMainInterest,userInterest,list);
    }
    //为用户推荐民族政策内容
    public List<VOPolicyList> recommendedPolicy(String uid){

        List<Double> userInterest = redisService.getUserInterest(uid,InterestSize-1).stream().map(o ->(Double)o).collect(Collectors.toList());
        Double[] doubles=new Double[userInterest.size()];
        doubles =userInterest.toArray(doubles);
        int n=new Random().nextInt(30);
        IPage<KeyWordsPolicy> page=new Page<>(n,100);
        List<KeyWordsPolicy> list = keyWordsPolicyService.page(page).getRecords();
        return getTop10CosPolicy(doubles,list);//获取第十一个特征矩阵,传入即可
    }

    //为搜索结果推荐内容
    public List<VOArticleList> recommendedSearchArticle(String articleId,String category){
        QueryWrapper<KeyWords> qw=new QueryWrapper<>();
        qw.eq("article_id",articleId);
        KeyWords one = keyWordsService.getOne(qw);
        keyWordJSONObject keyWordJSONObject = JSON.parseObject(one.getKeyWords(), keyWordJSONObject.class);
        if(keyWordJSONObject==null){
            return null;
        }
        Double[] articleDouble = keyWordJSONObject.getKeywords();

        int n=new Random().nextInt(50);
        IPage<KeyWords> page=new Page<>(n,200);
        QueryWrapper<KeyWords> qwkey=new QueryWrapper<>();
        qw.eq("category_name",category);
        List<KeyWords> list = keyWordsService.page(page,qwkey).getRecords();
        return getTop10CosArticle(articleDouble,list);
    }

    //为搜索结果推荐内容
    public List<VOPolicyList> recommendedSearchPolicy(String policyId){
        QueryWrapper<KeyWordsPolicy> qw=new QueryWrapper<>();
        qw.eq("policy_id",policyId);
        KeyWordsPolicy one = keyWordsPolicyService.getOne(qw);
        keyWordJSONObject keyWordJSONObject = JSON.parseObject(one.getKeyWords(), keyWordJSONObject.class);
        if(keyWordJSONObject==null){
            return null;
        }
        Double[] policyDouble = keyWordJSONObject.getKeywords();
        IPage<KeyWordsPolicy> page=new Page<>(1,1000);
        List<KeyWordsPolicy> list = keyWordsPolicyService.page(page).getRecords();
        return getTop10CosPolicy(policyDouble,list);
    }

    //根据传入的矩阵计计算出前十的文章信息（For Search）
    private List<VOArticleList> getTop10CosArticle(Double[] fromVector, List<KeyWords> articleKeyWords){
        PriorityQueue <Pair<Double,String>> pairPriorityQueue=new PriorityQueue<>(((o1, o2) -> {
            if(o1.getKey()>o2.getKey()) return -1;
            else if(o1.getKey().equals(o2.getKey())) return 0;
            else return 1;
        }));//优先队列，大顶堆筛选前10的文章信息
        //TreeMap<Double,String> treeMap=new TreeMap<>();
        List<String> keyword = articleKeyWords.stream().map(KeyWords::getKeyWords)
                .collect(Collectors.toList());

        for(int index=0;index<keyword.size();index++){
            String json=keyword.get(index);
            keyWordJSONObject keyWordJSONObject = JSON.parseObject(json, keyWordJSONObject.class);
            if(keyWordJSONObject==null){
                return null;
            }
            Double[] doubles=keyWordJSONObject.getKeywords();
            Double score=getCos(fromVector,doubles);
            //treeMap.put(score,articleKeyWords.get(index).getArticleId());
            pairPriorityQueue.add(new Pair<>(score,articleKeyWords.get(index).getArticleId()));
        }
        List<String> articleId=new ArrayList<>();
        /*
        for(int i=0;treeMap.size()!=0&&i<10;i++){//按照treemap取前十的值
            Map.Entry<Double, String> doubleStringEntry = treeMap.pollLastEntry();
            if(doubleStringEntry.getKey()!=1) {
                articleId.add(doubleStringEntry.getValue());
                articleId.add(Objects.requireNonNull(pairPriorityQueue.poll()).getValue());
            }

        }
        */
        for(int index=0;pairPriorityQueue.size()!=0&&index<10;){
            Pair<Double, String> poll = pairPriorityQueue.poll();
            if(poll!=null){
                logger.info("当前第"+index+"得分:"+poll.getKey());
                if(poll.getKey()!=1){
                    index++;
                    articleId.add(Objects.requireNonNull(poll).getValue());
                }
            }else{
                index++;
            }

        }
        List<Article> articles = articleService.listByIds(articleId);
        return articles.stream().map(article -> {
            VOArticleList voArticleList = new VOArticleList();
            DataChanges.fromArticleToVOArticleList(article, voArticleList);
            return voArticleList;
        }).collect(Collectors.toList());
    }

    //根据传入的矩阵计计算出前十的文章信息（For User）
    private List<VOArticleList> getTop10CosArticleForUser(List<Double> userMainInterest, List<List<Double>> userInterest, List<KeyWords> articleKeyWords) {
        PriorityQueue <Pair<Double,String>> pairPriorityQueue=new PriorityQueue<>(((o1, o2) -> {
            if(o1.getKey()>o2.getKey()) return -1;
            else if(o1.getKey().equals(o2.getKey())) return 0;
            else return 1;
        }));//优先队列，大顶堆筛选前10的文章信息
        //TreeMap<Double,String> treeMap=new TreeMap<>();
        List<String> keyword = articleKeyWords.stream().map(KeyWords::getKeyWords)
                .collect(Collectors.toList());

        for(int index=0;index<keyword.size();index++){
            String json=keyword.get(index);
            keyWordJSONObject keyWordJSONObject = JSON.parseObject(json, keyWordJSONObject.class);
            if(keyWordJSONObject==null){
                return null;
            }
            int i = interestList.indexOf(articleKeyWords.get(index).getCategoryName());//查找该文章属于哪个类别的下标
            List<Double> userInterestForIndex = userInterest.get(i);//获取该类别用户的兴趣矩阵
            Double[] userDoubles=new Double[userInterestForIndex.size()];
            userDoubles = userInterestForIndex.toArray(userDoubles);//转换为Double数组
            Double[] doubles=keyWordJSONObject.getKeywords();
            Double score=getCos(userDoubles,doubles);//计算cos值;
            //treeMap.put(score,articleKeyWords.get(index).getArticleId());
            Double weight=userMainInterest.get(i);//获取该category类别的权重，即用户对该类别文章的青睐程度
            pairPriorityQueue.add(new Pair<>(score*weight,articleKeyWords.get(index).getArticleId()));
        }
        List<String> articleId=new ArrayList<>();
        /*
        for(int i=0;treeMap.size()!=0&&i<10;i++){//按照treemap取前十的值
            Map.Entry<Double, String> doubleStringEntry = treeMap.pollLastEntry();
            if(doubleStringEntry.getKey()!=1) {
                articleId.add(doubleStringEntry.getValue());
                articleId.add(Objects.requireNonNull(pairPriorityQueue.poll()).getValue());
            }

        }
        */
        for(int index=0;pairPriorityQueue.size()!=0&&index<10;){
            Pair<Double, String> poll = pairPriorityQueue.poll();
            if(poll!=null){
                logger.info("当前第"+index+"得分:"+poll.getKey());
                if(poll.getKey()!=1){
                    index++;
                    articleId.add(Objects.requireNonNull(poll).getValue());
                }
            }else{
                index++;
            }

        }
        List<Article> articles = articleService.listByIds(articleId);
        return articles.stream().map(article -> {
            VOArticleList voArticleList = new VOArticleList();
            DataChanges.fromArticleToVOArticleList(article, voArticleList);
            return voArticleList;
        }).collect(Collectors.toList());
    }

    //for policy
    private List<VOPolicyList> getTop10CosPolicy(Double[] fromVector, List<KeyWordsPolicy> policyKeyWords){
        PriorityQueue <Pair<Double,String>> pairPriorityQueue=new PriorityQueue<>(((o1, o2) -> {
            if(o1.getKey()>o2.getKey()) return -1;
            else if(o1.getKey().equals(o2.getKey())) return 0;
            else return 1;
        }));//优先队列，大顶堆筛选前10的文章信息
        List<String> keyword = policyKeyWords.stream().map(KeyWordsPolicy::getKeyWords)
                .collect(Collectors.toList());

        for(int index=0;index<keyword.size();index++){
            String json=keyword.get(index);
            keyWordJSONObject keyWordJSONObject = JSON.parseObject(json, keyWordJSONObject.class);
            if(keyWordJSONObject==null){
                return null;
            }
            Double[] doubles=keyWordJSONObject.getKeywords();
            Double score=getCos(fromVector,doubles);
            pairPriorityQueue.add(new Pair<>(score,policyKeyWords.get(index).getPolicyId()));
        }
        List<String> policyId=new ArrayList<>();
        for(int index=0;pairPriorityQueue.size()!=0&&index<10;){
            Pair<Double, String> poll = pairPriorityQueue.poll();
            if(poll!=null){
                logger.info("当前第"+index+"得分:"+poll.getKey());
                if(poll.getKey()!=1){
                    index++;
                    policyId.add(Objects.requireNonNull(poll).getValue());
                }
            }else{
                index++;
            }
        }
        List<Policy> policies = policyListService.listByIds(policyId);

        return policies.stream().map(policy -> {
            VOPolicyList voPolicyList = new VOPolicyList();
            DataChanges.fromPolicyToVOPolicyList(policy, voPolicyList);
            return voPolicyList;
        }).collect(Collectors.toList());
    }

    private double getCos(Double[] fromVector, Double [] toVector){//计算余弦函数
        double x=0,y1=0,y2=0;
        for(int index=0;index<Math.min(fromVector.length,toVector.length);index++){
            x+=fromVector[index]*toVector[index];
            y1+=fromVector[index]*fromVector[index];
            y2+=toVector[index]*toVector[index];
        }
        if(y1*y2==0){
            return 0;
        }
        return x/Math.sqrt(y1*y2);
    }


    /**
     * 下面的函数是根据用户的显性操作和隐形操作来修改用户的兴趣特征矩阵，采用KNN算法
     * 这里主要设置一下权重，浏览的权重为1，收藏的权重为2.
     */


    //异步任务事务 隔离级别：可重复读 事务传播策略：需要开新的事务
    @Async("taskExecutorYZ")
    @Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRES_NEW)
    public void ChangeUserInterestArticleVectorByInterView(String uid, String id){//根据用户浏览修改用户兴趣矩阵
        double score=1.0;
        QueryWrapper<KeyWords> qw=new QueryWrapper<>();
        qw.eq("article_id",id);
        KeyWords one = keyWordsService.getOne(qw);
        if(one==null){
            throw new AuthenException(ResultType.NOTEXITS);
        }
        keyWordJSONObject keyWordJSONObject = JSON.parseObject(one.getKeyWords(), keyWordJSONObject.class);
        if(keyWordJSONObject==null){
            throw new AuthenException(ResultType.NOTEXITS);
        }
        Double[] keywords = keyWordJSONObject.getKeywords();
        List<Double> doubles = Arrays.asList(keywords);//获取文章的兴趣矩阵
        int type=interestList.indexOf(one.getCategoryName());//获取文章的类型，修改哪个用户的兴趣矩阵
        //修改redis缓存中的用户兴趣矩阵
        redisService.changeUserMainInterest(uid,type,score);
        redisService.changeUserInterest(uid,type,doubles);

    }
    @Async("taskExecutorYZ")
    @Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRES_NEW)
    public void ChangeUserInterestArticleVectorByCollect(String uid, String id,boolean isCollect){//根据用户收藏/取消收藏来修改用户兴趣矩阵
        double score=2.0;//该操作对用户对于每个分类的喜好权重的修改得分
        int weight=2;
        QueryWrapper<KeyWords> qw=new QueryWrapper<>();
        qw.eq("article_id",id);
        KeyWords one = keyWordsService.getOne(qw);
        if(one==null){
            throw new AuthenException(ResultType.NOTEXITS);
        }
        keyWordJSONObject keyWordJSONObject = JSON.parseObject(one.getKeyWords(), keyWordJSONObject.class);
        if(keyWordJSONObject==null){
            throw new AuthenException(ResultType.NOTEXITS);
        }
        Double[] keywords = keyWordJSONObject.getKeywords();

        //收藏对于用户兴趣矩阵应该修改的更多.这里取权重为2；
        for(int index=0;index<keywords.length;index++){
            keywords[index]*=weight;
            if(!isCollect){//如果为取消收藏，那么应该为负数
                keywords[index]=-keywords[index];
            }
        }
        List<Double> doubles = Arrays.asList(keywords);//获取文章的兴趣矩阵
        int type=interestList.indexOf(one.getCategoryName());//获取文章的类型，修改哪个用户的兴趣矩阵
        if(!isCollect){//操作为取消收藏
            score=-score;
        }
        //修改redis缓存中的用户兴趣矩阵
        redisService.changeUserMainInterest(uid,type,score);
        redisService.changeUserInterest(uid,type,doubles);

    }
    //异步任务事务 隔离级别：可重复读 事务传播策略：需要开新的事务
    @Async("taskExecutorYZ")
    @Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRES_NEW)
    public void ChangeUserInterestPolicyVectorByInterView(String uid, String id){//根据用户浏览修改用户兴趣矩阵

        QueryWrapper<KeyWordsPolicy> qw=new QueryWrapper<>();
        qw.eq("policy_id",id);
        KeyWordsPolicy one = keyWordsPolicyService.getOne(qw);
        if(one==null){
            throw new AuthenException(ResultType.NOTEXITS);
        }
        keyWordJSONObject keyWordJSONObject = JSON.parseObject(one.getKeyWords(), keyWordJSONObject.class);
        if(keyWordJSONObject==null){
            throw new AuthenException(ResultType.NOTEXITS);
        }
        Double[] keywords = keyWordJSONObject.getKeywords();//该政策的特征矩阵
        List<Double> doubles = Arrays.asList(keywords);//获取该政策的兴趣矩阵list
        redisService.changeUserInterest(uid,InterestSize-1,doubles);
    }
    @Async("taskExecutorYZ")
    @Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRES_NEW)
    public void ChangeUserInterestPolicyVectorByCollect(String uid, String id,boolean isCollect){//根据用户浏览修改用户兴趣矩阵
        int weight=2;
        QueryWrapper<KeyWordsPolicy> qw=new QueryWrapper<>();
        qw.eq("policy_id",id);
        KeyWordsPolicy one = keyWordsPolicyService.getOne(qw);
        if(one==null){
            throw new AuthenException(ResultType.NOTEXITS);
        }
        keyWordJSONObject keyWordJSONObject = JSON.parseObject(one.getKeyWords(), keyWordJSONObject.class);
        if(keyWordJSONObject==null){
            throw new AuthenException(ResultType.NOTEXITS);
        }
        Double[] keywords = keyWordJSONObject.getKeywords();//该政策的特征矩阵
        //收藏对于用户兴趣矩阵应该修改的更多.这里取权重为2；
        for(int index=0;index<keywords.length;index++){
            keywords[index]*=weight;
            if(!isCollect){//如果为取消收藏，那么应该为负数
                keywords[index]=-keywords[index];
            }
        }
        List<Double> doubles = Arrays.asList(keywords);//获取该政策的兴趣矩阵list
        redisService.changeUserInterest(uid,InterestSize-1,doubles);
    }
}
